﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;

using ARD.Common;
using ARD.Configuration;
using ARD.EventRouting;
using ARD.Logging;
using ARD.ServiceManagement;

using SharpObjects.SearchEngine.Common;
using SharpObjects.SearchEngine.Common.Indexing;
using SharpObjects.SearchEngine.Common.Search;
using SharpObjects.SearchEngine.Common.ApplicationEvents;
using SharpObjects.SearchEngine.Common.QueryConstraints;

namespace SharpObjects.SearchEngine_Test
{
    public class SearchEngineTest
    {
        static void Main(string[] args)
        {
            Location origin = new Location("SearchEngineTest.Main()");
            
            try
            {
                SharpObjects.SearchEngine_Test.App app = new SharpObjects.SearchEngine_Test.App();
                app.Arguments = args;
                app.Run();
            }
            catch (Exception ex)
            {
                App.Log.PublishError(origin, ex);
            }
        }
    }

    public class App 
    {
        public const string MainIndexDirectoryId = "main_index";
        public const string SystemConfigFile = "SharpObjects.SearchEngine_Test.config";
        private const string keyReferenceName = "app_key";
        public const string SystemKeyFile = "SharpObjects.SearchEngine_Test.key";

        public string[] Arguments { get; set; }
        static object syncRoot = new object();

        public static List<IndexDirectoryDescriptor> IndexDirectories { get; set; }
        public static string MainIndexDirectory { get; set; }

        public static EventBus DefaultEventBus { get; set; }

        public static ILoggingService LoggingService { get; set; }
        public static IIndexingService IndexingService { get; set; }
        public static ISearchService SearchService { get; set; }

        public static Logging Log { get; set; }

        static void InitializeApplication()
        {
            ServiceManager.Config = App.SystemConfig;

            // instantiate services
            DefaultEventBus = ServiceManager.Instance.GetService<EventBus>("default_event_bus");

            Log = new Logging(DefaultEventBus);

            ILoggingService logger = ServiceManager.Instance.GetService<ILoggingService>("logging_service");
            LoggingService = (ILoggingService) logger;

            IService indexer = ServiceManager.Instance.GetService<IService>("indexing_service");
            IndexingService = (IIndexingService)indexer;

            IndexDirectories = IndexingService.IndexDirectories;
            MainIndexDirectory = GetIndexDirectory(MainIndexDirectoryId);

            IService searcher = ServiceManager.Instance.GetService<IService>("search_service");
            SearchService = (ISearchService) searcher;
        }

        public static string GetIndexDirectory(string indexDirectoryId)
        {
            string indDir = (from dir in IndexDirectories
                                where dir.Id == indexDirectoryId
                                select dir.Path).Single();
            return indDir;
        }

        static App()
        {
            try
            {
                InitializeApplication();
            }
            catch (Exception ex)
            {
                
                Console.WriteLine("ERROR:\n" + ex.Message + "\n\n" + ex.StackTrace);
            }
        }


        public static XmlConfig SystemConfig
        {
            get
            {
                return GetSystemConfig();
            }
        }

        static XmlConfig GetSystemConfig()
        {
            lock (syncRoot)
            {
                XmlConfig config = new XmlConfig(SystemConfigFile, XmlConfig.eConfigOpenMode.OpenOrCreate);

                if (!config.SettingExists("/configuration/crypto_keys/key[@id='" + keyReferenceName + "']"))
                {
                    if (!File.Exists(SystemKeyFile))
                    {
                        XmlConfig.CreateCryptoKeyFile(SystemKeyFile);
                    }

                    config.AddCryptoKey(keyReferenceName, SystemKeyFile);
                    config.Save();
                }

                return config;
            }
        }


        public void Run()
        {
            Location origin = new Location("SharpObjects.SearchEngine_Test.App.Run()");

            App.Log.PublishInfo(origin, "Starting");

            //TestIndexingHIstory();

            //List<ContactInfo> contacts = GetContacts();
            //PopulateComments(ref contacts);
            //IndexContacts(contacts);

            IndexFiles();

            SearchInIndex();

            //SimilaritySearch();
        }

        public static class IndexFields 
        {
            // default fields that exist for every index document
            public const string uri_field = DefaultIndexFields.uri_field;                     // must be supplied by the indexing agent
            public const string date_indexed_field = DefaultIndexFields.date_indexed_field;   // is set up by the IndexingService
            public const string date_modified_field = DefaultIndexFields.date_modified_field; // must be supplied by the indexing agent

            // fields defined in this application
            
            
            public const string content_field = "content";
            public const string index_document_type_field = "document_type";
            public const string metadata_uri_field = "metadata_uri";
            public const string document_uri_field = "document_uri";
            public const string comment_field = "comment";
            public const string location_field = "location";
        
            // contact fields
            public const string first_name = "first_name";
            public const string last_name = "last_name";
            public const string address = "address";
            public const string phone_number = "phone_number";
            public const string email = "email";

        }

        public static class IndexDocumentType
        {
            public const string document = "document";
            public const string metatdata = "metadata";
        }

        public const string MetadataUriDelimiter = "?";
        public const string MetadataUriIndicator = "metadata";

        private readonly char[] pathSeparators = new char[] { '\\' };

        private void TestIndexingHIstory()
        {
            Location origin = new Location("SharpObjects.SearchEngine_Test.App.IndexFiles()");

            App.Log.PublishInfo(origin, "Starting Indexing History Test");

            IndexingHistory history = new IndexingHistory() {HistoryFilePath = "indexing_history_test.bin", IndexingStartTime = DateTime.Now};

            DateTime startLoading = DateTime.Now;
            history.Load();
            TimeSpan loadingDuration = DateTime.Now - startLoading;

            App.Log.PublishInfo(origin, "Indexing history loading duration: {0}", loadingDuration);

            for(int k = 0; k < 1000000; k++)
            {
                string uri = string.Format("this is a fake uri that simulates a real one - {0}", k.ToString("0000000"));
                history.UpdateEntry(uri, DateTime.Now, DateTime.Now);
            }

            App.Log.PublishInfo(origin, "Starting to save history");

            DateTime startSaving = DateTime.Now;
            history.Save();
            TimeSpan saveDuration = DateTime.Now - startSaving;

            App.Log.PublishInfo(origin, "Indexing history saving duration: {0}", saveDuration);

        }

        private List<string> indexedUris = new List<string>();

        private void IndexFiles()
        {
            Location origin = new Location("SharpObjects.SearchEngine_Test.App.IndexFiles()");

            indexedUris.Clear();

            if (Arguments.Length > 0)
            {
                string documentDirectoryPath = Arguments[0];

                DirectoryInfo documentDirectory = new DirectoryInfo(documentDirectoryPath);

                FileInfo[] fileInfos = documentDirectory.GetFiles();

                IndexingHistory history = new IndexingHistory() {HistoryFilePath = "indexing_history.bin", IndexingStartTime = DateTime.Now};
                history.Load();

                foreach (FileInfo fileInfo in fileInfos)
                {
                    try
                    {
                        string documentUri = fileInfo.FullName;
                        string metadataUri = documentUri + MetadataUriDelimiter + MetadataUriIndicator;

                        DateTime lastWriteTime = fileInfo.LastWriteTime;
                        DateTime indexedTime = DateTime.Now;

                        // CREATE DOCUMENT
                        ISourceDocument document = new SourceDocument() {Uri = documentUri};
                        string text = File.ReadAllText(document.Uri);

                        // POPULATE DOCUMENT
                        document.DateModified = lastWriteTime;
                        // StoreInIndex = true increases the size of the index, because the actual text is stored in the index
                        // By setting it to false all the statistical information is still preserved, but the 
                        // actual document text becomes not available to retrieve from the index
                        document.Fields.Add(new IndexField() {Name = IndexFields.content_field, Analyze = true, BuildTermVector = true, StoreInIndex = true, Text = text});
                        document.Fields.Add(new IndexField() {Name = IndexFields.index_document_type_field, Analyze = false, BuildTermVector = false, StoreInIndex = true, Text = IndexDocumentType.document});
                        document.Fields.Add(new IndexField() {Name = IndexFields.metadata_uri_field, Analyze = false, BuildTermVector = false, StoreInIndex = true, Text = metadataUri});

                        // INDEX DOCUMENT
                        IndexingApplicationEvent indDocEv = new IndexingApplicationEvent() {Document = document, IndexDirectoryId = App.MainIndexDirectoryId};
                        App.DefaultEventBus.Publish(indDocEv.GetType(), indDocEv);

                        history.UpdateEntry(documentUri, lastWriteTime, indexedTime);

                        indexedUris.Add(documentUri);

                        // CREATE METADATA DOCUMENT
                        ISourceDocument metadataDocument = new SourceDocument() {Uri = metadataUri};

                        // POPULATE METADATA
                        metadataDocument.DateModified = DateTime.Now;
                        metadataDocument.Fields.Add(new IndexField() {Name = IndexFields.location_field, Analyze = true, BuildTermVector = true, StoreInIndex = true, Text = GetLocations(documentUri)});

                        // INDEX METADATA
                        IndexingApplicationEvent indMetaEv = new IndexingApplicationEvent() { Document = metadataDocument, IndexDirectoryId = App.MainIndexDirectoryId };
                        App.DefaultEventBus.Publish(indMetaEv.GetType(), indMetaEv);

                        history.UpdateEntry(metadataUri, indexedTime, indexedTime);

                        indexedUris.Add(metadataUri);
                    }
                    catch (Exception ex)
                    {
                        App.Log.PublishError(origin, "Error indexing file: " + fileInfo.FullName);
                        App.Log.PublishError(origin, ex);
                    }
                }

                history.Save();

                List<string> deleted = history.GetDeleted();

                foreach(string uri in deleted)
                {
                    try
                    {
                        App.Log.PublishDebug(origin, "Deleting: '{0}'", uri);
                        ISourceDocument delDocument = new SourceDocument() {Uri = uri};
                        IndexingApplicationEvent delDocEv = new IndexingApplicationEvent() {Document = delDocument, IndexDirectoryId = App.MainIndexDirectoryId, IndexingAction = IndexingApplicationEvent.eIndexingAction.Delete};
                        App.DefaultEventBus.Publish(delDocEv.GetType(), delDocEv);
                        history.DeleteEntry(uri);
                    }
                    catch (Exception ex)
                    {
                        App.Log.PublishError(origin, "Error deleting URI: " + uri);
                        App.Log.PublishError(origin, ex);
                    }
                }

                history.Save();
            }
        }

        public class ContactInfo
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Address { get; set; }
            public List<string> Telephones { get; set; }
            public List<string> Emails { get; set; }

            public ContactInfo()
            {
                this.Telephones = new List<string>();
                this.Emails = new List<string>();
                this.Comments = null;
            }

            public string Uri
            {
                get
                {
                    return string.Format("contact:///contacts/{0}_{1}", this.FirstName, this.LastName);
                }
            }

            public Comments Comments { get; set;}

            public string GetText()
            {
                StringBuilder sb = new StringBuilder();

                sb.Append(this.FirstName);
                sb.Append(" ");
                sb.Append(this.LastName);
                sb.Append(" ");
                sb.Append(this.Address);
                sb.Append(" ");
                
                foreach(string telephone in this.Telephones)
                {
                    sb.Append(telephone);
                    sb.Append(" ");
                }

                foreach (string email in this.Emails)
                {
                    sb.Append(email);
                    sb.Append(" ");
                }

                if (this.Comments != null)
                    sb.Append(this.Comments.Text);

                return sb.ToString();
            }
        }

        public class Comments
        {
            public string Uri { get; set; }
            public string Text { get; set; }
        }

        private static List<ContactInfo> GetContacts()
        {
            List<ContactInfo> contacts = new List<ContactInfo>();

            ContactInfo contactInfo;

            {
                contactInfo = new ContactInfo() {FirstName = "John", LastName = "Smith"};
                contactInfo.Telephones.Add("1112223344");
                contactInfo.Telephones.Add("1112225566");
                contactInfo.Emails.Add("jsmith@live.com");
                contactInfo.Emails.Add("john.smith@gmail.com");
                contacts.Add(contactInfo);
            }

            {
                contactInfo = new ContactInfo() { FirstName = "Rick", LastName = "Brown" };
                contactInfo.Telephones.Add("2229992121");
                contactInfo.Emails.Add("r-brown@comcast.net");
                contactInfo.Emails.Add("rb_1980@yahoo.com");
                contacts.Add(contactInfo);
            }

            return contacts;
        }

        private static void PopulateComments(ref List<ContactInfo> contacts)
        {
            List<Comments> comments = new List<Comments>();

            if(contacts.Count > 0)
            {
                contacts[0].Comments = new Comments() {Uri = contacts[0].Uri, Text = "My comments"};
            }
        }

        private void IndexContacts(List<ContactInfo> contacts)
        {
            Location origin = new Location("SharpObjects.SearchEngine_Test.App.IndexContacts()");

            if (contacts.Count > 0)
            {

                IndexingHistory history = new IndexingHistory() { HistoryFilePath = "contact_indexing_history.bin", IndexingStartTime = DateTime.Now };
                history.Load();

                foreach (ContactInfo contactInfo in contacts)
                {
                    try
                    {
                        string documentUri = contactInfo.Uri;
                        string metadataUri = documentUri + MetadataUriDelimiter + MetadataUriIndicator;

                        DateTime lastWriteTime = DateTime.Now;
                        DateTime indexedTime = DateTime.Now;

                        // CREATE DOCUMENT
                        ISourceDocument document = new SourceDocument() { Uri = documentUri };
                        string text = contactInfo.GetText();

                        // POPULATE DOCUMENT
                        document.DateModified = lastWriteTime;
                        // StoreInIndex = true increases the size of the index, because the actual text is stored in the index
                        // By setting it to false all the statistical information is still preserved, but the 
                        // actual document text becomes not available to retrieve from the index
                        document.Fields.Add(new IndexField() { Name = IndexFields.content_field, Analyze = true, BuildTermVector = true, StoreInIndex = true, Text = text });
                        document.Fields.Add(new IndexField() { Name = IndexFields.index_document_type_field, Analyze = false, BuildTermVector = false, StoreInIndex = true, Text = IndexDocumentType.document });
                        document.Fields.Add(new IndexField() { Name = IndexFields.metadata_uri_field, Analyze = false, BuildTermVector = false, StoreInIndex = true, Text = metadataUri });

                        if (!string.IsNullOrEmpty(contactInfo.FirstName))
                        {
                            document.Fields.Add(new IndexField() { Name = IndexFields.first_name, Analyze = true, BuildTermVector = false, StoreInIndex = true, Text = contactInfo.FirstName });
                        }

                        if(!string.IsNullOrEmpty(contactInfo.LastName))
                        {
                            document.Fields.Add(new IndexField() { Name = IndexFields.last_name, Analyze = true, BuildTermVector = false, StoreInIndex = true, Text = contactInfo.LastName });
                        }

                        if (!string.IsNullOrEmpty(contactInfo.Address))
                        {
                            document.Fields.Add(new IndexField() { Name = IndexFields.address, Analyze = true, BuildTermVector = false, StoreInIndex = true, Text = contactInfo.Address });
                        }

                        foreach (string telephone in contactInfo.Telephones)
                        {
                            document.Fields.Add((new IndexField(){ Name = IndexFields.phone_number, Analyze = false, BuildTermVector = false, StoreInIndex = true, Text = telephone}));
                        }

                        foreach (string email in contactInfo.Emails)
                        {
                            document.Fields.Add((new IndexField() { Name = IndexFields.email, Analyze = true, BuildTermVector = false, StoreInIndex = true, Text = email }));
                        }

                        // INDEX DOCUMENT
                        IndexingApplicationEvent indDocEv = new IndexingApplicationEvent() { Document = document, IndexDirectoryId = App.MainIndexDirectoryId };
                        App.DefaultEventBus.Publish(indDocEv.GetType(), indDocEv);

                        history.UpdateEntry(documentUri, lastWriteTime, indexedTime);


                        if(contactInfo.Comments != null)
                        {
                            // CREATE METADATA DOCUMENT
                            ISourceDocument metadataDocument = new SourceDocument() { Uri = metadataUri };

                            // POPULATE METADATA
                            metadataDocument.DateModified = DateTime.Now;
                            metadataDocument.Fields.Add(new IndexField() { Name = IndexFields.comment_field, Analyze = true, BuildTermVector = true, StoreInIndex = true, Text = contactInfo.Comments.Text });

                            // INDEX METADATA
                            IndexingApplicationEvent indMetaEv = new IndexingApplicationEvent() { Document = metadataDocument, IndexDirectoryId = App.MainIndexDirectoryId };
                            App.DefaultEventBus.Publish(indMetaEv.GetType(), indMetaEv);

                            history.UpdateEntry(metadataUri, indexedTime, indexedTime);
                        }
                    }
                    catch (Exception ex)
                    {
                        App.Log.PublishError(origin, "Error indexing contact: " + contactInfo.Uri);
                        App.Log.PublishError(origin, ex);
                    }
                }

                history.Save();

                List<string> deleted = history.GetDeleted();

                foreach (string uri in deleted)
                {
                    try
                    {
                        App.Log.PublishDebug(origin, "Deleting: '{0}'", uri);
                        ISourceDocument delDocument = new SourceDocument() { Uri = uri };
                        IndexingApplicationEvent delDocEv = new IndexingApplicationEvent() { Document = delDocument, IndexDirectoryId = App.MainIndexDirectoryId, IndexingAction = IndexingApplicationEvent.eIndexingAction.Delete };
                        App.DefaultEventBus.Publish(delDocEv.GetType(), delDocEv);
                        history.DeleteEntry(uri);
                    }
                    catch (Exception ex)
                    {
                        App.Log.PublishError(origin, "Error deleting URI: " + uri);
                        App.Log.PublishError(origin, ex);
                    }
                }

                history.Save();
            }
        }




        private string GetLocations(string uri)
        {
            string[] pathParts = uri.Split(pathSeparators, StringSplitOptions.RemoveEmptyEntries);
            
            StringBuilder sb = new StringBuilder();

            foreach (string pathPart in pathParts)
            {
                sb.Append(pathPart);
                sb.Append(' ');
            }

            return sb.ToString();
        }

        private void SearchInIndex()
        {
            Location origin = new Location("SearchEngineTest.SearchInIndex()");

            App.Log.PublishInfo(origin, "Starting Search Test");

            Console.WriteLine("\n_______________________________________\n");
            TermQueryConstraint termQueryConstraint = new TermQueryConstraint()
                                                          {
                                                              FieldName = IndexFields.content_field, 
                                                              TermText = "newspaper"
                                                          };

            SearchApplicationEvent searchApplicationEvent = new SearchApplicationEvent()
                                                                {
                                                                    Constraint = termQueryConstraint, 
                                                                    IndexDirectoryId = App.MainIndexDirectoryId, 
                                                                    PageNumber = 0, 
                                                                    PageSize = 100
                                                                };

            App.DefaultEventBus.Subscribe(typeof(SearchResultApplicationEvent), new EventBus.EventHandler(SearchResultEventHandler));

            App.Log.PublishInfo(origin, "Starting Search Test");

            App.Log.PublishDebug(origin, "Searching for \"{0}\" ", termQueryConstraint);
            App.DefaultEventBus.Publish(searchApplicationEvent.GetType(), searchApplicationEvent);

            Console.Write("Enter your query [field:term] (or type 'quit' to exit): ");
            string input;

            while((input = Console.ReadLine()) != "quit")
            {
                App.Log.PublishDebug(origin, "Searching for: '{0}'", input);
                TextQueryConstraint textQueryConstraint = new TextQueryConstraint()
                                                              {
                                                                  Text = input
                                                              };
                searchApplicationEvent = new SearchApplicationEvent()
                                             {
                                                 Constraint = textQueryConstraint,
                                                 IndexDirectoryId = App.MainIndexDirectoryId,
                                                 PageNumber = 0,
                                                 PageSize = 100
                                             };
                App.DefaultEventBus.Publish(searchApplicationEvent.GetType(), searchApplicationEvent);

                Console.Write("Enter your query [field:term] (or type 'quit' to terminate): ");
            }
        }

        private void SimilaritySearch()
        {
            Location origin = new Location("SearchEngineTest.SimilaritySearch()");

            App.Log.PublishInfo(origin, "Similarity search");

            if (indexedUris.Count > 0)
            {
                App.Log.PublishInfo(origin, "Finding similar to: " + indexedUris[0]);

                SimilarDocumentQueryConstraint constraint = new SimilarDocumentQueryConstraint() {Uri = indexedUris[0]};
                constraint.FieldBoosts[IndexFields.content_field] = 3.0;

                SearchApplicationEvent searchApplicationEvent = new SearchApplicationEvent()
                                                                    {
                                                                        Constraint = constraint,
                                                                        IndexDirectoryId = App.MainIndexDirectoryId,
                                                                        PageNumber = 0,
                                                                        PageSize = 10
                                                                    };

                App.DefaultEventBus.Publish(searchApplicationEvent.GetType(), searchApplicationEvent);

            }
            else
            {
                App.Log.PublishInfo(origin, "No documents in index");
            }
        }
    
        private void SearchResultEventHandler(IApplicationEvent ev)
        {
            Location origin = new Location("SearchEngineTest.SearchREsultEventHandler()");


            if(ev is SearchResultApplicationEvent)
            {
                SearchResultApplicationEvent searchResultApplicationEvent = (SearchResultApplicationEvent) ev;

                Console.WriteLine("__________________\nFound {0} results\n__________________\n", searchResultApplicationEvent.QueryResult.QueryResultItems.Count);


                foreach (IQueryResultItem resultItem in searchResultApplicationEvent.QueryResult.QueryResultItems)
                {
                    string docName = Path.GetFileName(resultItem.Uri);
                    Console.WriteLine("=============\n[{0}] {1}", resultItem.Score.ToString("0.000"), docName);

                    double threshold;

                    if (resultItem.Fields[IndexFields.content_field].TermVector.Count > 0)
                    {
                        threshold = resultItem.Fields[IndexFields.content_field].TermVector[0].Score * 0.3;
                        int count = 0;

                        foreach (ITermVectorItem termVectorItem in resultItem.Fields[IndexFields.content_field].TermVector)
                        {
                            if (termVectorItem.Score >= threshold || count++ < 20)
                            {
                                Console.WriteLine("    {0} ({1})", termVectorItem.Term, termVectorItem.Score);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }

                }

                Console.WriteLine("--------------------------------\n");
            }
        }
    }
}
